﻿// =====================================================
// Copyright (C) 2016-2019 Fanjia
// All rights reserved
// 机器名称: PC-LIWEI
// 创建时间: 2019/3/19 10:13:46
// 创 建 人: 李伟
// ======================================================

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using FanjiaHotelPlatform.utils.Kits;
using Nest;
using WindowsFormsApplication1.Common;

namespace JobBatDeal.Common
{
    /// <summary>
    /// 字符串拓展类-用于文本相似度比较
    /// </summary>
    public static class SimilarityStringExtension
    {
        /// <summary>
        /// returns a string buffer of characters from string1 within string2 if they are of a given
        /// distance separation from the position in string1.
        /// </summary>
        /// <param name="firstWord">string one</param>
        /// <param name="secondWord">string two</param>
        /// <param name="distanceSep">separation distance</param>
        /// <returns>a string buffer of characters from string1 within string2 if they are of a given
        /// distance separation from the position in string1</returns>
        private static StringBuilder GetCommonCharacters(string firstWord, string secondWord, int distanceSep)
        {
            var returnCommons = new StringBuilder();
            var copy = new StringBuilder(secondWord);
            for (var i = 0; i < firstWord.Length; i++)
            {
                var ch = firstWord[i];
                var foundIt = false;
                for (var j = Math.Max(0, i - distanceSep);
                    !foundIt && j < Math.Min(i + distanceSep, secondWord.Length);
                    j++)
                {
                    if (copy[j] != ch) continue;
                    foundIt = true;
                    returnCommons.Append(ch);
                    copy[j] = '#';
                }
            }
            return returnCommons;
        }

        /// <summary>
        /// gets the similarity of the two strings using Jaro distance.
        /// </summary>
        /// <param name="firstWord"></param>
        /// <param name="secondWord"></param>
        /// <returns>a value between 0-1 of the similarity</returns>
        private static double GetJaroSimilarity(string firstWord, string secondWord)
        {
            //get half the length of the string rounded up - (this is the distance used for acceptable transpositions)
            var halfpence = Math.Max(firstWord.Length, secondWord.Length) / 2 + 1;
            //get common characters
            var common1 = GetCommonCharacters(firstWord, secondWord, halfpence);
            var commonMatches = common1.Length;
            //check for zero in common
            if (commonMatches == 0)
            {
                return 0;
            }
            var common2 = GetCommonCharacters(secondWord, firstWord, halfpence);
            //check for same length common strings returning 0.0f is not the same
            if (commonMatches != common2.Length)
            {
                return 0;
            }
            //get the number of transpositions
            var transpositions = 0;
            for (var i = 0; i < commonMatches; i++)
            {
                if (common1[i] != common2[i])
                {
                    transpositions++;
                }
            }

            //calculate jaro metric
            transpositions /= 2;
            var jaroSimilarity = commonMatches / (3.0 * firstWord.Length)
                       + commonMatches / (3.0 * secondWord.Length)
                       + (commonMatches - transpositions) / (3.0 * commonMatches);
            return jaroSimilarity;
        }

        /// <summary>
        /// gets the prefix length found of common characters at the begining of the strings.
        /// </summary>
        /// <param name="firstWord"></param>
        /// <param name="secondWord"></param>
        /// <returns>the prefix length found of common characters at the begining of the strings</returns>
        private static int GetPrefixLength(string firstWord, string secondWord)
        {
            var n = Math.Min(Math.Min(firstWord.Length, secondWord.Length), 4);
            for (var i = 0; i < n; i++)
            {
                if (firstWord[i] != secondWord[i])
                {
                    return i;
                }
            }
            return n;
        }

        /// <summary>
        /// 获取 JaroWinkler 相似度
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="target">目标字符串</param>
        /// <returns></returns>
        public static double GetJaroWinklerSimilarity(this string source, string target)
        {
            if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(target))
            {
                return 0;
            }

            var s = source.ToCleanStr();
            var t = target.ToCleanStr();
            if (s == t)
            {
                return 1;
            }

            var jaro = GetJaroSimilarity(s, t);
            var prefixLength = GetPrefixLength(s, t);
            return jaro + prefixLength * 0.1 * (1 - jaro);
        }

        /// <summary>
        /// 清理字符串
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string ToCleanStr(this string input)
        {
            return string.IsNullOrEmpty(input) ?
                string.Empty :
                Regex.Replace(input, @"[\s|\·|\.|\－|\—|\-|\(|\)|\【|\】|\（|\）]", "", RegexOptions.IgnoreCase);
        }

        /// <summary>
        /// 清理字符串（只保留数字）
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string ToCleanStrOnlyNum(this string input)
        {
            return string.IsNullOrEmpty(input) ?
                string.Empty :
                Regex.Replace(input, @"[^0-9]", "", RegexOptions.IgnoreCase);
        }

        /// <summary>
        /// 从XML字符串中反序列化对象
        /// </summary>
        /// <typeparam name="T">结果对象类型</typeparam>
        /// <param name="input">包含对象的XML字符串</param>
        /// <param name="encoding">编码方式</param>
        /// <returns>反序列化得到的对象</returns>
        public static T XmlDeserialize<T>(this string input, Encoding encoding = null)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            if (string.IsNullOrEmpty(input))
                throw new ArgumentNullException(input);
            var mySerializer = new XmlSerializer(typeof(T));
            using (var ms = new MemoryStream(encoding.GetBytes(input)))
            {
                using (var sr = new StreamReader(ms, encoding))
                {
                    return (T)mySerializer.Deserialize(sr);
                }
            }
        }
    }

    public static class GeoCoordinateExtension
    {
        private const double Xpi = Math.PI * 3000 / 180;

        /// <summary>
        /// 将百度坐标系（BD-09）转换成火星坐标系（GCJ-02）
        /// </summary>
        /// <param name="geoCoordinate"></param>
        /// <returns>火星坐标系的经纬度</returns>
        public static GeoCoordinate TransformBd09ToGcj02(this GeoCoordinate geoCoordinate)
        {
            var longitude = geoCoordinate.Longitude;
            var latitude = geoCoordinate.Latitude;
            if (longitude < 72.004 || longitude > 137.8347 || latitude < 0.8293 || latitude > 55.8271)
            {
                return default;
            }
            var x = longitude - 0.0065;
            var y = latitude - 0.006;
            var z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * Xpi);
            var theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * Xpi);
            return new GeoCoordinate(z * Math.Sin(theta), z * Math.Cos(theta));
        }

        /// <summary>
        /// 获取地理距离
        /// </summary>
        /// <param name="source">来源</param>
        /// <param name="target">目标</param>
        /// <returns>地理距离</returns>
        public static double GetGeoDistance(this GeoCoordinate source, GeoCoordinate target)
        {
            if (source == default || target == default)
            {
                return default;
            }

            if (Math.Abs(source.Longitude) > 180 || Math.Abs(source.Latitude) > 90 ||
                Math.Abs(target.Longitude) > 180 || Math.Abs(target.Latitude) > 90)
            {
                return default;
            }

            return source.GetDistanceTo(target);
        }

        /// <summary>
        /// 获取坐标相似度
        /// </summary>
        /// <param name="source">来源</param>
        /// <param name="target">目标</param>
        /// <returns>坐标相似度</returns>
        public static double GetCoordinateSimilarity(this GeoCoordinate source, GeoCoordinate target)
        {
            try
            {
                var geoDistance = source.GetGeoDistance(target);
                return geoDistance <= 100 ? 1 : geoDistance > 1000 ? 0 : Math.Floor((1100 - geoDistance) / 100) / 10;
            }
            catch (Exception e)
            {
                return 0;
            }
        }
    }

    public class SimilarityGenerator
    {
        public static double GetCoordinateSimilarity(
            decimal? supplierLatitude,
            decimal? supplierLongitude,
            decimal? fansCarLatitude,
            decimal? fansCarLongitude
        )
        {
            var coordinate = new GeoCoordinate(
                (double)(supplierLatitude ?? 0M),
                (double)(supplierLongitude ?? 0M));

            var fansCarCoordinate = new GeoCoordinate(
                (double)(fansCarLatitude ?? 0M),
                (double)(fansCarLongitude ?? 0M));


            var hotelCoordinateSimilarity = coordinate.GetCoordinateSimilarity(fansCarCoordinate);

            return hotelCoordinateSimilarity;
        }

        public static double GetCoordinateSimilarity(
            double? supplierLatitude,
            double? supplierLongitude,
            double? fansCarLatitude,
            double? fansCarLongitude
        )
        {
            var coordinate = new GeoCoordinate(
                supplierLatitude ?? 0D,
                supplierLongitude ?? 0D);

            var fansCarCoordinate = new GeoCoordinate(
                fansCarLatitude ?? 0D,
                fansCarLongitude ?? 0D);

            var hotelCoordinateSimilarity = coordinate.GetCoordinateSimilarity(fansCarCoordinate);

            return hotelCoordinateSimilarity;
        }

        public static double GetNameSimilarity(string supplierHotelName, string hotelName)
        {
            // 酒店名称相似度
            var hotelNameSimilarity = supplierHotelName.GetJaroWinklerSimilarity(hotelName);
            return hotelNameSimilarity;
        }

        public static decimal GetPhoneSimilarity(string supplierHotelPhone, string hotelPhone)
        {
            if (string.IsNullOrEmpty(supplierHotelPhone) || string.IsNullOrEmpty(hotelPhone))
            {
                return 0;
            }

            var hotelPhoneSimilarity = string.IsNullOrWhiteSpace(supplierHotelPhone) ||
                                       supplierHotelPhone.StartsWith(@"\n") ||
                                       supplierHotelPhone.StartsWith("400") ||
                                       supplierHotelPhone.ToCleanStr() != hotelPhone.ToCleanStr() ? 0 : 1;

            return hotelPhoneSimilarity;
        }

        public static decimal GetPhoneSimilarityRmPrex(string supplierHotelPhone, string hotelPhone)
        {
            if (string.IsNullOrEmpty(supplierHotelPhone) || string.IsNullOrEmpty(hotelPhone))
            {
                return 0;
            }

            supplierHotelPhone = supplierHotelPhone.Replace("+86-", "0");

            if (supplierHotelPhone.IndexOf("0086-", StringComparison.Ordinal) == 0)
            {
                supplierHotelPhone = supplierHotelPhone.Replace("0086-", "");
            }
            if (supplierHotelPhone.IndexOf("86-", StringComparison.Ordinal) == 0)
            {
                supplierHotelPhone = supplierHotelPhone.Replace("86-", "");
            }

            hotelPhone = hotelPhone.Replace("+86-", "0");
            if (hotelPhone.IndexOf("0086-", StringComparison.Ordinal) == 0)
            {
                hotelPhone = hotelPhone.Replace("0086-", "");
            }
            if (hotelPhone.IndexOf("86-", StringComparison.Ordinal) == 0)
            {
                hotelPhone = hotelPhone.Replace("86-", "");
            }

            //过滤最后两位分机号-0
            supplierHotelPhone = RemoveEndNum(supplierHotelPhone);
            hotelPhone = RemoveEndNum(hotelPhone);

            if (supplierHotelPhone.Contains("转") && !hotelPhone.Contains("转"))
            {
                supplierHotelPhone = RemoveEndStr(supplierHotelPhone, "转");
            }
            if (!supplierHotelPhone.Contains("转") && hotelPhone.Contains("转"))
            {
                hotelPhone = RemoveEndStr(hotelPhone, "转");
            }

            var hotelPhoneSimilarity = string.IsNullOrWhiteSpace(supplierHotelPhone) ||
                                       supplierHotelPhone.StartsWith(@"\n") ||
                                       supplierHotelPhone.StartsWith("400") ||
                                       supplierHotelPhone.ToCleanStrOnlyNum() != hotelPhone.ToCleanStrOnlyNum() ? 0 : 1;

            if (hotelPhoneSimilarity == 0)
            {
                var splitStr = new string[] { "/", ";", "," };
                if (supplierHotelPhone.Length > 13 && hotelPhone.Length <= 13)
                {
                    foreach (var split in splitStr)
                    {
                        if (supplierHotelPhone.Contains(split))
                        {
                            var sp = supplierHotelPhone.Split(split);

                            foreach (var s in sp)
                            {
                                var ss = RemoveEndNum(s);
                                hotelPhoneSimilarity = string.IsNullOrWhiteSpace(ss) ||
                                           ss.StartsWith(@"\n") ||
                                           ss.StartsWith("400") ||
                                           ss.ToCleanStrOnlyNum() != hotelPhone.ToCleanStrOnlyNum() ? 0 : 1;

                                if (hotelPhoneSimilarity == 1)
                                    break;

                                if (hotelPhoneSimilarity == 0)
                                {
                                    var hPhone = ss.ToCleanStrOnlyNum();
                                    var sPhone = hotelPhone.ToCleanStrOnlyNum();

                                    if (hPhone.Length >= 11 && sPhone.Length >= 11)
                                    {
                                        hPhone = hPhone.Substring(hPhone.Length - 11);
                                        sPhone = sPhone.Substring(sPhone.Length - 11);

                                        if (hPhone == sPhone)
                                        {
                                            hotelPhoneSimilarity = 1;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if (hotelPhone.Length > 13 && supplierHotelPhone.Length <= 13)
                {
                    foreach (var split in splitStr)
                    {
                        if (hotelPhone.Contains(split))
                        {
                            var sp = hotelPhone.Split(split);

                            foreach (var s in sp)
                            {
                                var ss = RemoveEndNum(s);

                                hotelPhoneSimilarity = string.IsNullOrWhiteSpace(ss) ||
                                           ss.StartsWith(@"\n") ||
                                           ss.StartsWith("400") ||
                                           ss.ToCleanStrOnlyNum() != supplierHotelPhone.ToCleanStrOnlyNum() ? 0 : 1;

                                if (hotelPhoneSimilarity == 1)
                                    break;

                                if (hotelPhoneSimilarity == 0)
                                {
                                    var hPhone = ss.ToCleanStrOnlyNum();
                                    var sPhone = supplierHotelPhone.ToCleanStrOnlyNum();

                                    if (hPhone.Length >= 11 && sPhone.Length >= 11)
                                    {
                                        hPhone = hPhone.Substring(hPhone.Length - 11);
                                        sPhone = sPhone.Substring(sPhone.Length - 11);

                                        if (hPhone == sPhone)
                                        {
                                            hotelPhoneSimilarity = 1;
                                            break;
                                        }
                                    }
                                }
                            }

                        }
                    }
                }

                if (hotelPhoneSimilarity == 0 && hotelPhone.Length >= 12 && supplierHotelPhone.Length >= 12)
                {
                    var hotelPhoneList = new List<string>();
                    var spHotelPhoneList = new List<string>();

                    foreach (var split in splitStr)
                    {
                        if (hotelPhone.Contains(split))
                        {
                            var sp = hotelPhone.Split(split);

                            foreach (var s in sp)
                            {
                                var ss = RemoveEndNum(s).ToCleanStrOnlyNum();

                                if (ss.Length >= 11 && !ss.StartsWith("400") && !ss.StartsWith(@"\n"))
                                    hotelPhoneList.Add(ss);
                            }
                        }

                        if (supplierHotelPhone.Contains(split))
                        {
                            var sp = supplierHotelPhone.Split(split);

                            foreach (var s in sp)
                            {
                                var ss = RemoveEndNum(s).ToCleanStrOnlyNum();

                                if (ss.Length >= 11 && !ss.StartsWith("400") && !ss.StartsWith(@"\n"))
                                    spHotelPhoneList.Add(ss);
                            }
                        }
                    }

                    if (hotelPhoneList.Count == 0)
                    {
                        hotelPhoneList.Add(RemoveEndNum(hotelPhone).ToCleanStrOnlyNum());
                    }

                    if (spHotelPhoneList.Count == 0)
                    {
                        spHotelPhoneList.Add(RemoveEndNum(supplierHotelPhone).ToCleanStrOnlyNum());
                    }

                    if (hotelPhoneList.Count > 0 && spHotelPhoneList.Count > 0)
                    {
                        foreach (var hp in hotelPhoneList)
                        {
                            foreach (var sp in spHotelPhoneList)
                            {
                                hotelPhoneSimilarity = hp != sp ? 0 : 1;

                                if (hotelPhoneSimilarity == 1)
                                    break;

                                if (hotelPhoneSimilarity == 0)
                                {
                                    var hPhone = hp.Length >= 10 ? hp.Substring(hp.Length - 10) : hp;
                                    var sPhone = sp.Length >= 10 ? sp.Substring(sp.Length - 10) : sp;

                                    if (hPhone == sPhone)
                                    {
                                        hotelPhoneSimilarity = 1;
                                        break;
                                    }
                                }
                            }

                            if (hotelPhoneSimilarity == 1)
                                break;
                        }
                    }
                }
            }

            if (hotelPhoneSimilarity == 0 && !supplierHotelPhone.StartsWith("400") && !supplierHotelPhone.StartsWith(@"\n"))
            {
                var hPhone = hotelPhone.ToCleanStrOnlyNum();
                var sPhone = supplierHotelPhone.ToCleanStrOnlyNum();

                var rLen = 10;

                if (hPhone.Length >= rLen && sPhone.Length >= rLen)
                {
                    hPhone = hPhone.Substring(hPhone.Length - rLen);
                    sPhone = sPhone.Substring(sPhone.Length - rLen);

                    if (hPhone == sPhone)
                        return 1;
                }
            }

            return hotelPhoneSimilarity;
        }

        private static string RemoveEndNum(string phoneStr)
        {
            if (phoneStr.Length > 7)
            {
                var i = phoneStr.LastIndexOf('-');
                if (i > 0 && i == phoneStr.Length - 2)
                {
                    phoneStr = phoneStr.Substring(0, i);
                }
            }
            return phoneStr;
        }

        public static string RemoveEndStr(string phoneStr, string splitStr)
        {
            if (phoneStr.Length > 7)
            {
                var i = phoneStr.LastIndexOf(splitStr);
                if (i > 0)
                {
                    phoneStr = phoneStr.Substring(0, i);
                }
            }
            return phoneStr;
        }
    }
}
